/*
 *
 *
Copyright (c) 1998-2001 Aristar, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any,
must include the following acknowledgment:

"This product includes software developed by Aristar, Inc. (http://www.aristar.com/)."

Alternately, this acknowledgment may appear in the software itself, if and
wherever such third-party acknowledgments normally appear.

4. The names "Dialect" and "Aristar, Inc." must not be used to endorse or
promote products derived from this software without prior written permission.
For written permission, please contact info@aristar.com.

5. Products derived from this software may not be called "Dialect", nor may
"Dialect" appear in their name, without prior written permission of Aristar, Inc.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ARISTAR, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software consists of voluntary contributions made by many individuals
on behalf of Aristar, Inc.  For more information on Aristar, Inc.,
please see <http://www.aristar.com/>.

Portions of this software are based upon software freely available and
written by individuals not associated with Aristar, Inc.  Copyrights are
maintained in the relevant source code files.
 *
 *
 */

// CodeEditWnd.cpp : implementation file
//

#include "stdafx.h"
#include "DialectApp.h"

#ifdef IDE_SUPPORT

#include "CodeEditWnd.h"
#include "DialectView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define EDIT_AREA	1
#define MARGIN_AREA	2
#define MOUSEMOVEUP_TIMER		42333
#define MOUSEMOVEDOWN_TIMER		42334
#define FLASH_TIMER				34990

#define ALT_DOWN (GetKeyState(VK_MENU) & 0x8000)
#define SHIFT_DOWN (GetKeyState(VK_SHIFT) & 0x8000)
#define CONTROL_DOWN (GetKeyState(VK_CONTROL) & 0x8000)

// The number of spaces for a tab is TABSIZE - MODTABSIZE(x)
#define TABSIZE 4
#define MODTABSIZE(x)	((x) & 0x3)		// Probably faster than (x % 4)
static CString tabs[4] = {_T("    "), _T("   "), _T("  "), _T(" ")};

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

const CString crMac(_T("\r"));
const CString crWindows(_T("\r\n"));
const CString crUnix(_T("\n"));

CTextBuffer::CTextBuffer()
{
	m_UndoStack = new CUndoStack(64);
	SetText(_T(""));
}

CTextBuffer::CTextBuffer(CString text)
{
	m_UndoStack = new CUndoStack(64);
	SetText(text);
}

CTextBuffer::~CTextBuffer()
{
	Clear();	// Free strings
	delete m_UndoStack;
}

void CTextBuffer::Clear()
{
	// Need to delete all the CLinesOfText we allocated
	while (!m_Lines.IsEmpty()) {
		delete m_Lines.RemoveHead();
	}
	m_UndoStack->Clear();
}

int CTextBuffer::LineCount()
{
	return m_Lines.GetCount();
}

int CTextBuffer::LineLength(int line)
{
	return GetLineOfText(line).GetLength();
}

void CTextBuffer::GetLinesOfText(int startLine, int count, CString lines[], bool breakpoints[])
{
	POSITION p = m_Lines.FindIndex(startLine);
	for (int idx = 0; idx < count; ++idx) {
		if (p != NULL) {
			CLineOfText* lot = (CLineOfText*)m_Lines.GetAt(p);
			lines[idx] = lot->m_Text;
			breakpoints[idx] = lot->m_bBreakPoint;
			m_Lines.GetNext(p);
		}
		else {
			lines[idx].Empty();
			breakpoints[idx] = false;
		}
	}
}

CString CTextBuffer::GetLineOfText(int line)
{
	CLineOfText* lot = GetCLineOfText(line);
	if (lot == NULL) return _T("");
	else return lot->m_Text;
}

CString CTextBuffer::GetText(CString crStyle, int startLine, int startCol, int endLine, int endCol)
{
	CString result;
	POSITION p = m_Lines.FindIndex(startLine);
	if (startLine == endLine) {
		if (p != NULL) {
			CLineOfText* lot = (CLineOfText*)m_Lines.GetAt(p);
			if (lot != NULL) {
				result = lot->m_Text.Mid(startCol, endCol - startCol);
			}
		}
	}
	else {
		for (int idx = startLine; idx <= endLine; ++idx) {
			if (p == NULL) break;

			if (idx == startLine) {
				result += ((CLineOfText*)m_Lines.GetAt(p))->m_Text.Mid(startCol);
			}
			else if (idx == endLine) {
				result += ((CLineOfText*)m_Lines.GetAt(p))->m_Text.Left(endCol);
			}
			else {
				result += ((CLineOfText*)m_Lines.GetAt(p))->m_Text;
			}
			if (idx != endLine) result += crStyle;

			m_Lines.GetNext(p);
		}
	}
	return result;
}

CString CTextBuffer::GetText(CString crStyle)
{
	// crStyle is a string to be used as the carriage return chars
	// Three strings are predefined above: crMac, crWindows, and crUnix

	CString result;
	POSITION pos = m_Lines.GetHeadPosition();
	while (pos != NULL) {
		CLineOfText* lot = (CLineOfText*)m_Lines.GetAt(pos);
		result += lot->m_Text;
		m_Lines.GetNext(pos);
		if (pos != NULL) result += crStyle;
	}
	return result;
}

CString CTextBuffer::GetText(CString crStyle, int firstLine, int lastLine)
{
	// crStyle is a string to be used as the carriage return chars
	// Three strings are predefined above: crMac, crWindows, and crUnix

	ASSERT(false);	// Don't know if we need this version yet...
	return _T("");
}

void CTextBuffer::SetText(LPCTSTR text)
{
	Clear();	// Free what was there before

	// Always have at least one item in list (even though it might empty)
	m_Lines.AddTail(new CLineOfText(_T(""), false));

	if ((text == NULL) || (*text == '\0')) {
		// Always have at least one item in list (even though empty)
		//m_Lines.AddTail(new CLineOfText(_T(""), false));
		return;
	}
	Insert(text, 0, 0);
}

CTextBuffer::CLineOfText* CTextBuffer::GetCLineOfText(int line)
{
	// The first line is line zero (not one)
	if (line < 0) return NULL;
	POSITION pos = m_Lines.FindIndex(line);
	if (pos == NULL) return NULL;
	else return (CLineOfText*)m_Lines.GetAt(pos);
}

bool CTextBuffer::IsBreakpoint(int line)
{
	CLineOfText* lot = GetCLineOfText(line);
	if (lot == NULL) return false;
	else return lot->m_bBreakPoint;
}

void CTextBuffer::SetBreakpoint(int line, bool bSet)
{
	CLineOfText* lot = GetCLineOfText(line);

	if (lot == NULL) return;

	if (bSet && !lot->m_bBreakPoint) {
		lot->m_bBreakPoint = true;
		++m_BreakPointCount;
	}
	else if (!bSet && lot->m_bBreakPoint){
		lot->m_bBreakPoint = false;
		--m_BreakPointCount;
	}
}

int CTextBuffer::BreakPointCount()
{
	return m_BreakPointCount;
}

CPoint CTextBuffer::Insert(TCHAR ch, int line, int column)
{
	ASSERT(line < m_Lines.GetCount());

	CLineOfText* lot = GetCLineOfText(line);
	if (ch == '\n') {
		POSITION p = m_Lines.FindIndex(line);
		if (column == 0) {
			m_Lines.InsertBefore(p, new CLineOfText(_T(""), false));
		}
		else {
			m_Lines.InsertAfter(p, new CLineOfText(lot->m_Text.Mid(column), false));
			lot->m_Text = lot->m_Text.Left(column);
		}
		return CPoint(0, line + 1);
	}
	else {
		lot->m_Text = lot->m_Text.Left(column) + ch + lot->m_Text.Mid(column);
		return CPoint(column + 1, line);
	}
}

CPoint CTextBuffer::Insert(CString str, int line, int column)
{
	// Insert all given text starting at line,col
	// Pushing text back if needed

	ASSERT(line < m_Lines.GetCount());
	int count = 0;
	LPCTSTR lineStart = str, text = str;

	POSITION p = m_Lines.FindIndex(line);
	ASSERT(p != NULL);
	if (p == NULL) return CPoint(column, line);

	CLineOfText* lot = (CLineOfText*)m_Lines.GetAt(p);
	CString startText = lot->m_Text.Left(column);
	CString endText = lot->m_Text.Mid(column);
	POSITION temp = p;
	delete m_Lines.GetPrev(p);	// Delete p->lot then set p to previous
	m_Lines.RemoveAt(temp);
	--line;

	// Extract each individual line from the text and
	// remove all forms of carriage return: \r, \r\n, and \n
	while (true) {
		switch (*text++) {
			case '\r':
				if (*text == '\n') ++text;
				// Fall through to '\n' case
			case '\n':
				// Finished with line, add it to the list of lines
				if (p == NULL) {
					p = m_Lines.AddHead(new CLineOfText(startText + CString(lineStart, count), false));
				}
				else {
					p = m_Lines.InsertAfter(p, new CLineOfText(startText + CString(lineStart, count), false));
				}
				++line;	// Keep track of current line number
				startText.Empty();
				count = 0;
				lineStart = text;
				break;
			case '\0':
				// All done
				if (p == NULL) {
					m_Lines.AddHead(new CLineOfText(startText + CString(lineStart, count) + endText, false));
				}
				else {
					m_Lines.InsertAfter(p, new CLineOfText(startText + CString(lineStart, count) + endText, false));
				}
				++line;
				return CPoint(startText.GetLength() + count, line);
			default:
				++count;
		}
	}
}

void CTextBuffer::Delete(int line, int column)
{
	// Delete character (column of -1 mean we concat we previous line)
	ASSERT(line < m_Lines.GetCount());

	CLineOfText* lot = GetCLineOfText(line);
	ASSERT(column <= lot->m_Text.GetLength());

	if (column == lot->m_Text.GetLength()) {
		// If the column is one greater than line length, pull next line up
		// Concat this and next
		POSITION p = m_Lines.FindIndex(line + 1);
		if (p != NULL) {
			CLineOfText* oldLine = (CLineOfText*)m_Lines.GetAt(p);
			lot->m_Text = lot->m_Text + oldLine->m_Text;
			m_Lines.RemoveAt(p);
			delete oldLine;
		}
	}
	else if (column >= 0) {
		lot->m_Text = lot->m_Text.Left(column) + lot->m_Text.Mid(column + 1);
	}
	else if (line >= 0) {
		// Concat two lines (previous and this)
		POSITION p = m_Lines.FindIndex(line - 1);
		if (p != NULL) {
			CLineOfText* newLine = (CLineOfText*)m_Lines.GetAt(p);
			newLine->m_Text = newLine->m_Text + lot->m_Text;
			// Now free the original
			m_Lines.GetNext(p);	// p becomes position of current line (line)
			m_Lines.RemoveAt(p);
			delete lot;
		}
	}
}

CPoint CTextBuffer::Replace(CString str, int startLine, int startCol,
							int endLine, int endCol)
{
	// This is darn slow! And messy.  Need to revamp breakpoint handling, or something...

	// This replace is used from outside the buffer and stores undo info
	ASSERT(endLine < m_Lines.GetCount());
	CPoint caret;
	CString oldText = GetText(crWindows, startLine, startCol, endLine, endCol);
	bool bBreakPoint = IsBreakpoint(startLine);
	bool bEndBreakPoint = startLine != endLine ? IsBreakpoint(endLine) : false;
	Delete(startLine, startCol, endLine, endCol);
	if (str.IsEmpty()) {
		caret = CPoint(startCol, startLine);
		if ((startCol == 0) && (endCol == 0)) {
			if (bBreakPoint) SetBreakpoint(startLine, false);
			if (bEndBreakPoint) SetBreakpoint(caret.y, true);
		}
		else {
			if (bBreakPoint) SetBreakpoint(startLine, true);
			if (bEndBreakPoint) SetBreakpoint(caret.y, true);
		}
	}
	else if (str.GetLength() == 1) {
		caret = Insert((TCHAR)str[0], startLine, startCol);
	}
	else {
		caret = Insert(str, startLine, startCol);
		if (bBreakPoint) SetBreakpoint(startLine, true);
		if (bEndBreakPoint) SetBreakpoint(caret.y, true);
	}

	m_UndoStack->Push(new CUndoItem(utPaste, str, startLine, startCol, caret.y, caret.x,
		oldText, endLine, endCol));
	return caret;
}

CPoint CTextBuffer::_Replace(CString str, int startLine, int startCol, int endLine, int endCol)
{
	// Same as above replace, but used by paste, so it doesn't register Undo info
	ASSERT(endLine < m_Lines.GetCount());
	Delete(startLine, startCol, endLine, endCol);
	if (str.IsEmpty()) return CPoint(startCol, startLine);
	else return Insert(str, startLine, startCol);
}

void CTextBuffer::Delete(int startLine, int startCol, int endLine, int endCol)
{
	POSITION p = m_Lines.FindIndex(startLine);
	if (p == NULL) return;
	CLineOfText* lot = (CLineOfText*)m_Lines.GetAt(p);

	if (startLine == endLine) {
		if (startCol != endCol) {
			lot->m_Text = lot->m_Text.Left(startCol) + lot->m_Text.Mid(endCol);
		}
	}
	else {
		CLineOfText* lot2;
		m_Lines.GetNext(p);
		for (int idx = startLine + 1; idx < endLine; ++idx) {
			if (p == NULL) return;
			lot2 = (CLineOfText*)m_Lines.GetAt(p);
			delete lot2;
			POSITION temp = p;
			m_Lines.GetNext(p);
			m_Lines.RemoveAt(temp);
		}
		lot2 = (CLineOfText*)m_Lines.GetAt(p);
		lot->m_Text = lot->m_Text.Left(startCol) + lot2->m_Text.Mid(endCol);
		delete lot2;
		m_Lines.RemoveAt(p);
	}
}

static bool SetClipboardText(CString text)
{
	int textLength = text.GetLength();

	if (::OpenClipboard(NULL)) {
		::EmptyClipboard();
#ifdef UNDER_CE
		HLOCAL hMem = LocalAlloc(LMEM_FIXED, (textLength + 1) * sizeof(TCHAR));
		if (hMem == NULL) {
			::CloseClipboard();
			return false;
		}
		LPSTR lpStr = (LPSTR)LocalLock(hMem);
		memcpy(lpStr, (LPCTSTR)text, (textLength + 1) * sizeof(TCHAR));	// Copies NULL terminator also
		LocalUnlock(hMem);
		::SetClipboardData(CF_UNICODETEXT, hMem);
#else
		HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, (textLength + 1) * sizeof(TCHAR));
		if (hMem == NULL) {
			::CloseClipboard();
			return false;
		}
		LPSTR lpStr = (LPSTR)GlobalLock(hMem);
		memcpy(lpStr, (LPCTSTR)text, (textLength + 1) * sizeof(TCHAR));	// Copies NULL terminator also
		GlobalUnlock(hMem);
		::SetClipboardData(CF_TEXT, hMem);
#endif
		::CloseClipboard();
		return true;
	}
	return false;
}


static CString GetClipboardText()
{
    CString result;
    if (OpenClipboard(NULL)) {
	#ifdef UNDER_CE
        if (IsClipboardFormatAvailable(CF_UNICODETEXT)) {
            result = (LPCTSTR)GetClipboardData(CF_UNICODETEXT);
        }
        else if (IsClipboardFormatAvailable(CF_TEXT)) {
            result = (LPCSTR)GetClipboardData(CF_TEXT);
        }
	#else
        if (IsClipboardFormatAvailable(CF_TEXT)) {
            HGLOBAL hMem = GetClipboardData(CF_TEXT);
            LPSTR lpStr = (LPSTR)GlobalLock(hMem);
            result = lpStr;
            GlobalUnlock(hMem);
        }
	#endif
       CloseClipboard();
    }
    return result;
}

void CTextBuffer::Copy(int startLine, int startCol, int endLine, int endCol)
{
	CString text = GetText(crWindows, startLine, startCol, endLine, endCol);
	SetClipboardText(text);
}

void CTextBuffer::Cut(int startLine, int startCol, int endLine, int endCol)
{
	CString text = GetText(crWindows, startLine, startCol, endLine, endCol);
	SetClipboardText(text);
	Delete(startLine, startCol, endLine, endCol);
	m_UndoStack->Push(new CUndoItem(utCut, text, startLine, startCol, endLine, endCol));
}

CPoint CTextBuffer::Paste(int startLine, int startCol, int endLine, int endCol)
{
	CString text = GetClipboardText();
	CString oldText = GetText(crWindows, startLine, startCol, endLine, endCol);
	CPoint caret = _Replace(text, startLine, startCol, endLine, endCol);
	m_UndoStack->Push(new CUndoItem(utPaste, text, startLine, startCol, caret.y, caret.x,
		oldText, endLine, endCol));
	return caret;
}

CPoint CTextBuffer::CommentOut(int startLine, int startCol, int endLine, int endCol)
{
	startCol = 0;
	// Cut Text
	CString oldText = GetText(crWindows, startLine, startCol, endLine, endCol);
	// Do commenting
	CString newText = _T("#") + oldText;
	int pos = 1;
	while ((pos = newText.Find('\n', pos)) > 0) {
		pos++;
		if (pos != newText.GetLength()) {
			newText.Insert(pos, '#');
		}
	}

	// Paste it back
	CPoint caret = _Replace(newText, startLine, startCol, endLine, endCol);
	m_UndoStack->Push(new CUndoItem(utPaste, newText, startLine, startCol, caret.y, caret.x,
		oldText, endLine, endCol));
	return caret;
}

CPoint CTextBuffer::Uncomment(int startLine, int startCol, int endLine, int endCol)
{
	startCol = 0;
	// Cut Text
	CString oldText = GetText(crWindows, startLine, startCol, endLine, endCol);
	// Uncomment
	CString newText = oldText;
	if ('#' == newText.GetAt(0))
		newText.Delete(0,1);

	int pos = 1;
	while ((pos = newText.Find(_T("\n#"), pos)) > 0) {
		pos++;
		if (pos != (newText.GetLength() - 1)) {
			newText.Delete(pos, 1);
		}
	}

	// Paste it back
	CPoint caret = _Replace(newText, startLine, startCol, endLine, endCol);
	m_UndoStack->Push(new CUndoItem(utPaste, newText, startLine, startCol, caret.y, caret.x,
		oldText, endLine, endCol));
	return caret;
}

CPoint CTextBuffer::Indent(int startLine, int startCol, int endLine, int endCol)
{
	startCol = 0;
	// Cut Text
	CString oldText = GetText(crWindows, startLine, startCol, endLine, endCol);
	// Do commenting
	CString newText = _T("\t") + oldText;
	int pos = 1;
	while ((pos = newText.Find('\n', pos)) > 0) {
		pos++;
		if (pos != newText.GetLength()) {
			newText.Insert(pos, '\t');
		}
	}

	// Paste it back
	CPoint caret = _Replace(newText, startLine, startCol, endLine, endCol);
	m_UndoStack->Push(new CUndoItem(utPaste, newText, startLine, startCol, caret.y, caret.x,
		oldText, endLine, endCol));
	return caret;
}

CPoint CTextBuffer::Unindent(int startLine, int startCol, int endLine, int endCol)
{
	startCol = 0;
	// Cut Text
	CString oldText = GetText(crWindows, startLine, startCol, endLine, endCol);
	// Uncomment
	CString newText = oldText;
	if ('\t' == newText.GetAt(0))
		newText.Delete(0,1);

	int pos = 1;
	while ((pos = newText.Find(_T("\n\t"), pos)) > 0) {
		pos++;
		if (pos != (newText.GetLength() - 1)) {
			newText.Delete(pos, 1);
		}
	}

	// Paste it back
	CPoint caret = _Replace(newText, startLine, startCol, endLine, endCol);
	m_UndoStack->Push(new CUndoItem(utPaste, newText, startLine, startCol, caret.y, caret.x,
		oldText, endLine, endCol));
	return caret;
}

CPoint CTextBuffer::Undo(CPoint initCaretPos)
{
	CUndoItem* ui = m_UndoStack->Pop();
	if (ui != NULL) {
		CPoint newCaretPos;
		switch (ui->m_Type) {
			case utCut:
				newCaretPos = Insert(ui->m_Text, ui->m_StartLine, ui->m_StartCol);
				break;
			case utPaste:
				newCaretPos = _Replace(ui->m_OldText, ui->m_StartLine, ui->m_StartCol,
					ui->m_EndLine, ui->m_EndCol);
				break;
		}
		return newCaretPos;
	}
	return initCaretPos;
}

CPoint CTextBuffer::Redo(CPoint initCaretPos)
{
	CUndoItem* ui = m_UndoStack->PopRedo();
	if (ui != NULL) {
		CPoint newCaretPos;
		switch (ui->m_Type) {
			case utCut:
				Delete(ui->m_StartLine, ui->m_StartCol, ui->m_EndLine, ui->m_EndCol);
				newCaretPos = CPoint(ui->m_StartCol, ui->m_StartLine);
				break;
			case utPaste:
				newCaretPos = _Replace(ui->m_Text, ui->m_StartLine, ui->m_StartCol,
					ui->m_DelToLine, ui->m_DelToCol);
				break;
		}
		return newCaretPos;
	}
	return initCaretPos;
}

bool CTextBuffer::CanUndo()
{
	return !m_UndoStack->IsEmpty();
}

bool CTextBuffer::CanRedo()
{
	return !m_UndoStack->IsRedoEmpty();
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CCodeEditWnd

CCodeEditWnd::CCodeEditWnd()
{
	m_bShowLineNumbers =
		AfxGetApp()->GetProfileInt(_T("Options"), _T("ShowLineNums"), 1) == 1;

	m_bShift = false;
	//m_bControl = false;
	m_bLButtonDown = false;
	m_TopLine = 0;
	m_LeftCol = 0;
	m_SelStart = CPoint(0, 0);
	m_SelEnd = CPoint(0, 0);
	m_CurrentLineMark = -1;
	m_bModified = false;
	m_bReadOnly = false;
	m_MarginWidth = 50;

	// Set a few defaults...
	m_rgbText = RGB(0, 0, 0);
	m_rgbMarginText = RGB(0, 0, 0);
	m_rgbBackground = RGB(255, 255, 255);
	m_rgbMarginBackground = RGB(192, 192, 192);

	// Get our brushes...
	m_BackgroundBrush.CreateSolidBrush(m_rgbBackground);
	m_MarginBackgroundBrush.CreateSolidBrush(m_rgbMarginBackground);
	m_BreakPointBrush.CreateSolidBrush(RGB(255, 0, 0));

	// Some fonts...
#ifdef WIN32_PLATFORM_PSPC
	m_Font.CreatePointFont(80, _T("Courier New"));
#else
	m_Font.CreatePointFont(100, _T("Courier New"));
#endif

	TEXTMETRIC tm;
	HDC hDC = ::GetDC(NULL);
	::SelectObject(hDC, m_Font);
	::GetTextMetrics(hDC, &tm);
	::ReleaseDC(NULL, hDC);

	// Only support monotype fonts
	m_FontWidth = tm.tmAveCharWidth;
	m_FontHeight = tm.tmHeight;

	m_CaretPos = CPoint(0, 0);
}

CCodeEditWnd::~CCodeEditWnd()
{
	// The destructors for these objects probably already
	// call DeleteObject(), but we'll do it just to be safe
	m_BackgroundBrush.DeleteObject();
	m_MarginBackgroundBrush.DeleteObject();
	m_BreakPointBrush.DeleteObject();
	m_Font.DeleteObject();
}

#if defined(UNDER_CE) //&& (UNDER_CE <= 200)
#	define ON_WM_SETCURSOR()
#endif
BEGIN_MESSAGE_MAP(CCodeEditWnd, CWnd)
	//{{AFX_MSG_MAP(CCodeEditWnd)
	ON_WM_PAINT()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()
	ON_WM_SETCURSOR()
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
	ON_WM_CHAR()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_MOUSEMOVE()
	ON_WM_SIZE()
	ON_WM_VSCROLL()
	ON_WM_TIMER()
	ON_WM_HSCROLL()
	ON_WM_LBUTTONDBLCLK()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CCodeEditWnd message handlers

bool CCodeEditWnd::GetShowLineNumbers()
{
	return m_bShowLineNumbers;
}

void CCodeEditWnd::SetShowLineNumbers(bool bShow)
{
	if (bShow != m_bShowLineNumbers) {
		m_bShowLineNumbers = bShow;
		InvalidateRect(NULL);
	}
}

int CCodeEditWnd::LinesPerScreen()
{
	RECT rc;
	GetClientRect(&rc);
	return ((rc.bottom - rc.top) / m_FontHeight) + 1;
}

int CCodeEditWnd::ColsPerScreen()
{
	RECT rc;
	GetClientRect(&rc);
	return ((rc.right - rc.left - m_MarginWidth) / m_FontWidth) + 1;
}

int CCodeEditWnd::LineCount()
{
	return m_TextBuffer.LineCount();
}

void CCodeEditWnd::SetText(CString text)
{
	m_TextBuffer.SetText(text);
	InvalidateRect(NULL);
}

CString CCodeEditWnd::GetText()
{
	return m_TextBuffer.GetText(crWindows);
}

CString CCodeEditWnd::GetLineOfText(int line)
{
	return m_TextBuffer.GetLineOfText(line);
}

bool CCodeEditWnd::IsBreakpoint(int line)
{
	return m_TextBuffer.IsBreakpoint(line);
}

void CCodeEditWnd::SetBreakpoint(int line, bool bBreak)
{
	if (line == -1) line = m_CaretPos.y;
	m_TextBuffer.SetBreakpoint(line, bBreak);
}

void CCodeEditWnd::ToggleBreakpoint(int line)
{
	if (line == -1) line = m_CaretPos.y;
	m_TextBuffer.SetBreakpoint(line, !m_TextBuffer.IsBreakpoint(line));
	InvalidateRect(NULL);
}

void CCodeEditWnd::SetModified(bool bModified)
{
	// Since this is making another func call, and this could get called
	// on every key press, only notify when state has really changed
	// ie. If we're already set to modified, don't do it again

	if (bModified && !m_bModified) {
		m_bModified = true;
		CDialectView* pParent = (CDialectView*)GetParent();
		ASSERT(pParent != NULL);
		pParent->EditModified(true);
	}
	else if (!bModified && m_bModified) {
		m_bModified = false;
		CDialectView* pParent = (CDialectView*)GetParent();
		ASSERT(pParent != NULL);
		pParent->EditModified(false);
	}
}

bool CCodeEditWnd::GetModified()
{
	return m_bModified;
}

void CCodeEditWnd::SetReadOnly(bool bReadOnly)
{
	m_bReadOnly = bReadOnly;
}

void CCodeEditWnd::PaintLine(CDC& dc, CString text, CRect& rc, int line,
							 CPoint& start, CPoint& end)
{
	int left, right;	// Section of rc to highlight

	// Convert all tabs to appropriate number of spaces
	// This isn't the most efficient way to do this...
	for (int idx = 0; idx < text.GetLength(); ++idx) {
		if (text[idx] == '\t') {
			text = text.Left(idx) + tabs[MODTABSIZE(idx)] + text.Mid(idx + 1);
		}
	}

	// Set selection rect coordinates for later
	if (start == end) {
		left = right = 0;
	}
	else {
		if ((line > start.y) && (line < end.y)) {
			left = rc.left;
			right = rc.left + ((text.GetLength() + 1 - m_LeftCol) * m_FontWidth);
		}
		else if ((line == start.y) || (line == end.y)) {
			if (line == start.y) {
				CPoint s = start;
				TabAdjust(s, true);		// Yuck
				left = rc.left + (s.x - m_LeftCol) * m_FontWidth;
			}
			else left = rc.left;

			if (line == end.y) {
				CPoint e = end;
				TabAdjust(e, true);		// Yuck
				right = rc.left + (e.x - m_LeftCol) * m_FontWidth;
			}
			else right = rc.left + ((text.GetLength() + 1 - m_LeftCol) * m_FontWidth);
		}
	}

	if (m_LeftCol > 0) {
		// Remove any text that is left of the left margin
		if (text.GetLength() >= m_LeftCol) text = text.Mid(m_LeftCol);
		else text.Empty();
	}

	dc.ExtTextOut(rc.left, rc.top, ETO_OPAQUE,	rc, text, NULL);

	if (left < rc.left) left = rc.left;
	if (right > rc.right) right = rc.right;
	if (right - left > 0) {
		dc.PatBlt(left, rc.top, right - left, m_FontHeight, DSTINVERT);
	}
}

void CCodeEditWnd::OnPaint()
{
	CPaintDC dc(this); // device context for painting

	RECT rc;
	GetClientRect(&rc);

	int linesInBuffer = m_TextBuffer.LineCount();
	CString temp, formatSpec;
	if (m_bShowLineNumbers) {
		temp.Format(_T("  %d "), linesInBuffer);
		m_MarginWidth = temp.GetLength() * m_FontWidth;
		formatSpec.Format(_T("  %%%dd "), temp.GetLength() - 3);
	}
	else {
		m_MarginWidth = 3 * m_FontWidth;
		formatSpec = _T("   ");
	}

	CString lines[96];	// Absolute max lines per screen
	bool breakpoints[96];
	int linesPerScreen = LinesPerScreen();
	m_TextBuffer.GetLinesOfText(m_TopLine, linesPerScreen <= 96 ? linesPerScreen : 96,
		lines, breakpoints);

	CPoint end;
	CPoint start;
	CorrectSelOrder(start, end);

	dc.SelectObject(m_Font);
	for (int idx = 0; idx < linesPerScreen; ++idx) {
		int y = idx * m_FontHeight;

		// Draw the margin area...
		dc.SetBkColor(m_rgbMarginBackground);
		if (idx + m_TopLine < linesInBuffer) {
			temp.Format(formatSpec, m_TopLine + idx + 1);	// + 1 for user
			//dc.DrawText(temp, CRect(0, y, m_MarginWidth, y + m_FontHeight), DT_LEFT | DT_SINGLELINE);
			dc.ExtTextOut(0, y, ETO_OPAQUE, CRect(0, y, m_MarginWidth, y + m_FontHeight), temp, NULL);
			if (breakpoints[idx]) {
				CBrush* oldBrush = dc.SelectObject(&m_BreakPointBrush);
				int diameter = m_FontWidth + (m_FontWidth >> 1);
				dc.Ellipse(0, y + (diameter >> 2), diameter, y + diameter);
				dc.SelectObject(oldBrush);
			}
		}
		else {
			dc.FillSolidRect(0, y, m_MarginWidth, m_FontHeight, m_rgbMarginBackground);
		}

		// Set up background color and maybe draw a triangle
		if (m_CurrentLineMark == idx + m_TopLine) {
			// Draw triangle...
			int mid = y + m_FontHeight / 2;
			for(int j = 0; j <= 6; j++) {
				dc.MoveTo(m_MarginWidth - j - 1, mid - j);
				dc.LineTo(m_MarginWidth - j - 1, mid + j);
			}
			dc.SetBkColor(RGB(200, 235, 235));	// Highlight line special color
		}
		else {
			dc.SetBkColor(m_rgbBackground);
		}

		PaintLine(dc, lines[idx],
			CRect(m_MarginWidth, y, rc.right, y + m_FontHeight),
			idx + m_TopLine, start, end);
	}
	// We need to replace the caret becuase m_MarginWidth may have changed
	// do to a change in the number of lines of text
	PlaceCaret(m_CaretPos.y, m_CaretPos.x, selMaintain);

	// Do not call CWnd::OnPaint() for painting messages
}

BOOL CCodeEditWnd::PreTranslateMessage(MSG* pMsg)
{
	if ((pMsg->message == WM_KEYDOWN) && CONTROL_DOWN) {
		switch (pMsg->wParam) {
			case 'C':	Copy();		return TRUE;
			case 'V':	Paste();	return TRUE;
			case 'X':	Cut();		return TRUE;
			case 'Z':	Undo();		return TRUE;
			case 'Y':	Redo();		return TRUE;
			case 'A':	SelectAll();return TRUE;
		}
	}
	return CWnd::PreTranslateMessage(pMsg);
}

int CCodeEditWnd::Create(int style, const RECT& rc, CWnd* parent, int id)
{
	return CWnd::Create(NULL, NULL, style, rc, parent, id);
}

BOOL CCodeEditWnd::PreCreateWindow(CREATESTRUCT& cs)
{
	if (!CWnd::PreCreateWindow(cs))
		return FALSE;

	cs.dwExStyle |= WS_EX_CLIENTEDGE;
	cs.style &= ~WS_BORDER;

	return TRUE;
}

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

void CCodeEditWnd::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);	// Probably not needed, but it don't hurt

	// If the window is getting larger and the last visible
	// line of text is the last line in the text buffer, then
	// we must scroll the contents down with the window or
	// else we could end up with the scroll bar vanishing
	// while there is still text out of view (course, the
	// cursor keys would scroll it into view, but it's
	// disorienting)
	static int lastcy = cy;
	if ((lastcy < cy) && (m_TopLine + LinesPerScreen() > m_TextBuffer.LineCount())) {
		ScrollUpDown(m_TextBuffer.LineCount() - LinesPerScreen() + 1);
	}
	lastcy = cy;
	AdjustScrollBarSizes();
}

void CCodeEditWnd::OnSetFocus(CWnd* pOldWnd)
{
	AdjustScrollBarSizes();
	CWnd::OnSetFocus(pOldWnd);
	CreateSolidCaret(2, m_FontHeight);
	PlaceCaret(m_CaretPos.y, m_CaretPos.x, selMaintain);
	ShowCaret();
}

void CCodeEditWnd::OnKillFocus(CWnd* pNewWnd)
{
	CWnd::OnKillFocus(pNewWnd);
	HideCaret();
	::DestroyCaret();
}

#if !defined(UNDER_CE)// || (UNDER_CE > 200)
BOOL CCodeEditWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
	if (nHitTest == HTCLIENT) {
		POINT pos;
		GetCursorPos(&pos);
		ScreenToClient(&pos);
		if (pos.x < m_MarginWidth) {
			SetCursor(LoadCursor(NULL, IDC_ARROW));
		}
		else {
			SetCursor(LoadCursor(NULL, IDC_IBEAM));
		}
		return TRUE;
	}
	return CWnd::OnSetCursor(pWnd, nHitTest, message);
}
#endif

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

int CCodeEditWnd::PointToArea(CPoint point)
{
	return point.x < m_MarginWidth - m_FontWidth ? MARGIN_AREA : EDIT_AREA;
}

void CCodeEditWnd::SelectWordAt(int line, int column)
{
	// Double clicked, select encompassing word
	if ((line >= 0) && (line < m_TextBuffer.LineCount())) {
		CString text = m_TextBuffer.GetLineOfText(line);
		if (column >= text.GetLength()) column = text.GetLength() - 1;
		int start = column;
		int end = column;
		while ((start >= 0) && (_istalnum(text[start]) || (text[start] == '_')))
			start--;
		while ((end < text.GetLength()) && (_istalnum(text[end]) || (text[end] == '_')))
			end++;
		PlaceCaret(line, start + 1, selRemove);
		PlaceCaret(line, end, selExtend);
	}
}

void CCodeEditWnd::OnLButtonDblClk(UINT nFlags, CPoint point)
{
	// The hot point of the I-beam cursor is it's center, so adjust
	CPoint lineCol = ClientToLineCol(point, true);
	SelectWordAt(lineCol.y, lineCol.x);
}

void CCodeEditWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
	SetCapture();
	CPoint lineCol;
	switch (PointToArea(point)) {
		case EDIT_AREA:
			m_bLButtonDown = true;	// With this true, the text selection follows

			// The hot point of the I-beam cursor is it's center, so adjust
			lineCol = ClientToLineCol(point, true);
			PlaceCaret(lineCol.y, lineCol.x, selRemove);
			break;
		case MARGIN_AREA:
			{
			// Toggle breakpoints...
			lineCol = ClientToLineCol(point, false);
			m_TextBuffer.SetBreakpoint(lineCol.y, !m_TextBuffer.IsBreakpoint(lineCol.y));
			RECT rc = {
				0,
				(lineCol.y - m_TopLine) * m_FontHeight,
				m_MarginWidth,
				(lineCol.y - m_TopLine) * m_FontHeight + m_FontHeight
			};
			InvalidateRect(&rc);
			}
			break;
	}
}

void CCodeEditWnd::OnLButtonUp(UINT nFlags, CPoint point)
{
	KillTimer(MOUSEMOVEUP_TIMER);	// Just in case
	KillTimer(MOUSEMOVEDOWN_TIMER);	// Just in case
	m_bLButtonDown = false;
	ReleaseCapture();
}

void CCodeEditWnd::OnMouseMove(UINT nFlags, CPoint point)
{
	if (m_bLButtonDown) {
		RECT rc;
		GetClientRect(&rc);
		if (point.y < 0) {
			CaretUp(true);
			SetTimer(MOUSEMOVEUP_TIMER, 50, NULL);
		}
		else if (point.y > rc.bottom - rc.top) {
			CaretDown(true);
			SetTimer(MOUSEMOVEDOWN_TIMER, 50, NULL);
		}
		else {
			CPoint lineCol;
			if (point.x < m_MarginWidth) {
				point.x = m_MarginWidth;
			}
			// The hot point of the I-beam cursor is it's center, so adjust
			lineCol = ClientToLineCol(point, true);
			PlaceCaret(lineCol.y, lineCol.x, selExtend);
		}
	}
}

void CCodeEditWnd::OnTimer(UINT nIDEvent)
{
	if (nIDEvent == FLASH_TIMER) {
		KillTimer(FLASH_TIMER);
		SetCurrentLineMark(-1);
		return;
	}

	CPoint point;
#ifdef UNDER_CE
	// Can we say "Hack"?
	point = CPoint(m_MarginWidth + 3, nIDEvent == MOUSEMOVEUP_TIMER ? -1 : 3000);
#else
	GetCursorPos(&point);
	ScreenToClient(&point);
#endif

	if (nIDEvent == MOUSEMOVEUP_TIMER) {
		KillTimer(MOUSEMOVEUP_TIMER);
		OnMouseMove(MK_LBUTTON, point);
	}
	else if (nIDEvent == MOUSEMOVEDOWN_TIMER) {
		KillTimer(MOUSEMOVEDOWN_TIMER);
		OnMouseMove(MK_LBUTTON, point);
	}
	else CWnd::OnTimer(nIDEvent);
}

void CCodeEditWnd::FlashLine(int line, COLORREF color)
{
	PlaceCaret(line, 0, selRemove, true);
	SetCurrentLineMark(line);
	SetTimer(FLASH_TIMER, 2000, NULL);
}

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

void CCodeEditWnd::TabAdjust(CPoint& point, bool bExpand)
{
	// bExpand == true means for every tab left of point.x inc point.x by tabsize
	// bExpand == false means for every tab left of point.x dec point.x by tabsize
	// Watch out for when the incoming column happens to be in the middle of a tab

	int column = point.x;	// The requested column to adjust
	LPCTSTR line = m_TextBuffer.GetLineOfText(point.y);

	if (bExpand) {
		int idx = 0;
		int newColumn = 0;
		while (line[idx] && (idx < column)) {
			if (line[idx++] == '\t') newColumn += (TABSIZE - MODTABSIZE(newColumn));
			else ++newColumn;
		}
		point.x = newColumn;
	}
	else {
		int idx = 0;
		int curColumn = 0;
		//curColumn -= m_LeftCol;

		while (line[idx] && (column > curColumn)) {
			if (line[idx++] == '\t') {
				curColumn += (TABSIZE - MODTABSIZE(curColumn));
			}
			else {
				++curColumn;
			}
		}
		point.x = idx;
	}
}

CPoint CCodeEditWnd::ClientToLineCol(CPoint point, bool bNudge)
{
	// Take a point in client coords and figure out what line and
	// column it hits.
	CPoint result;
	result.x = ((point.x - m_MarginWidth) / m_FontWidth) + m_LeftCol;
	result.y = (point.y / m_FontHeight) + m_TopLine;
	if (bNudge) {
		// Nudging is used to slide the cursor location when clicking
		// directly on a character (left half, nudge left, vice versa)
		if (((point.x - m_MarginWidth) % m_FontWidth) > (m_FontWidth >> 2)) {
			++result.x;	// Nudge it
		}
	}
	TabAdjust(result, false);
	return result;
}

CPoint CCodeEditWnd::LineColToClient(CPoint point)
{
	// Take line and column and return client coordinate
	CPoint result;
	TabAdjust(point, true);
	result.x = ((point.x - m_LeftCol) * m_FontWidth) + m_MarginWidth;
	result.y = (point.y - m_TopLine) * m_FontHeight;
	return result;
}

void CCodeEditWnd::PlaceCaret(int line, int column, SelectionOp selOption, bool bScroll)
{
	// Given line and col, place cursor immediately after the character
	// at that position.  If bScroll, then scroll caret into view if not there already.

	line = line < 0 ? 0 : line;
	column = column < 0 ? 0 : column;
	line = line >= m_TextBuffer.LineCount() ? m_TextBuffer.LineCount() - 1 : line;
	column = column > m_TextBuffer.LineLength(line) ? m_TextBuffer.LineLength(line) : column;

	// In case we want to invalidate only the newly highlighted region...
	// ...to avoid undo flickering
	int top = ((line < m_CaretPos.y ? line : m_CaretPos.y) - m_TopLine) * m_FontHeight;
	int bottom = ((line < m_CaretPos.y ? m_CaretPos.y : line) + 1 - m_TopLine) * m_FontHeight;

	m_CaretPos = CPoint(column, line);
	CPoint ptTemp = LineColToClient(m_CaretPos);
	SetCaretPos(ptTemp);

	// Don't allow the caret to enter the margin column!
	static bool bHidden = false;	// Initially shown when created
	//if (bHidden && m_CaretPos.x >= m_LeftCol) {
	if (bHidden && ptTemp.x >= m_MarginWidth) {
		ShowCaret();
		bHidden = false;
	}
	//else if (!bHidden && m_CaretPos.x < m_LeftCol) {
	else if (!bHidden && ptTemp.x < m_MarginWidth) {
		HideCaret();
		bHidden = true;
	}

	if (selOption == selExtend) {
		m_SelEnd = m_CaretPos;
		CRect rc(m_MarginWidth, top, 2048, bottom);
		InvalidateRect(&rc);
	}
	else if (selOption == selRemove) {
		// Remove selection
		if (m_SelStart != m_SelEnd) {
			InvalidateRect(NULL);
			m_SelStart = m_SelEnd = m_CaretPos;
		}
		else {
			m_SelStart = m_SelEnd = m_CaretPos;
		}
	}

	if (bScroll) {
		if (m_CaretPos.y < m_TopLine) {
			ScrollUpDown(m_CaretPos.y);
		}
		else if (m_CaretPos.y > m_TopLine + LinesPerScreen() - 2) {
			ScrollUpDown(m_CaretPos.y - LinesPerScreen() + 2);
		}

		if (m_CaretPos.x < m_LeftCol) {
			ScrollLeftRight(m_CaretPos.x);
		}
		else {
			CPoint temp = m_CaretPos;
			TabAdjust(temp, true);
			if (temp.x > m_LeftCol + ColsPerScreen() - 2) {
				ScrollLeftRight(temp.x - ColsPerScreen() + 2);
			}
		}
	}

	/*CString str;
	str.Format(_T("Line: %d, Col: %d to Line: %d, Col %d\n"), m_SelStart.y, m_SelStart.x,
		m_SelEnd.y, m_SelEnd.x);
	OutputDebugString(str);*/
}


void CCodeEditWnd::CaretLeft(bool bExtendSel)
{
	int line = m_CaretPos.y;
	int col = m_CaretPos.x - 1;
	if ((col < 0) && (line > 0)) {
		col = m_TextBuffer.LineLength(--line);
	}
	PlaceCaret(line, col, m_bShift || bExtendSel ? selExtend : selRemove, true);
}

void CCodeEditWnd::CaretRight(bool bExtendSel)
{
	int line = m_CaretPos.y;
	int col = m_CaretPos.x + 1;
	int lineCount = m_TextBuffer.LineCount();
	int lineLen = m_TextBuffer.LineLength(line);

	if (col > lineLen) {
		if (line + 1 == lineCount) return;	// Reach end of text buffer
		else {
			col = 0;
			++line;
		}
	}
	PlaceCaret(line, col, m_bShift || bExtendSel ? selExtend : selRemove, true);
}

void CCodeEditWnd::CaretUp(bool bExtendSel)
{
	CPoint pt = m_CaretPos;
	TabAdjust(pt, true);
	pt.y--;	// Move up one line
	if (pt.y >= 0) {
		TabAdjust(pt, false);
		int prevLineLen = m_TextBuffer.LineLength(pt.y);
		if (pt.x > prevLineLen) pt.x = prevLineLen;
		PlaceCaret(pt.y, pt.x, m_bShift || bExtendSel ? selExtend : selRemove, true);
	}
}

void CCodeEditWnd::CaretDown(bool bExtendSel)
{
	CPoint pt = m_CaretPos;
	TabAdjust(pt, true);
	pt.y++;	// Move down one line
	if (pt.y < m_TextBuffer.LineCount()) {
		TabAdjust(pt, false);
		int nextLineLen = m_TextBuffer.LineLength(pt.y);
		if (pt.x > nextLineLen) pt.x = nextLineLen;
		PlaceCaret(pt.y, pt.x, m_bShift || bExtendSel ? selExtend : selRemove, true);
	}
}

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

void CCodeEditWnd::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	switch (nChar) {
		case VK_RETURN:
			ReplaceSel(_T("\n"));
			break;
		case VK_BACK:
			if (m_SelStart == m_SelEnd) {
				m_SelStart = m_SelEnd = m_CaretPos;
				CaretLeft(true);
			}
			ReplaceSel(_T(""));
			break;
		case 'C': case 'V': case 'X': case 'Y': case 'A': case 'Z':
			// Cut, copy, paste, etc.
			if (CONTROL_DOWN) return;
			// else fall through to default
		case VK_TAB:
			if (!CONTROL_DOWN && !ALT_DOWN && (m_SelStart != m_SelEnd)) {
				if (SHIFT_DOWN) {
					Unindent();
				}
				else {
					Indent();
				}
			}
			else {
				ReplaceSel(nChar);
			}
			break;
		default:
			ReplaceSel(nChar);
	}
	InvalidateRect(NULL);
}

void CCodeEditWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	switch (nChar) {
		case VK_SHIFT:
			m_bShift = true;
			break;
		case VK_CONTROL:
			//m_bControl = true;
			break;
		case VK_LEFT:
			CaretLeft();
			break;
		case VK_RIGHT:
			CaretRight();
			break;
		case VK_UP:
			CaretUp();
			break;
		case VK_DOWN:
			CaretDown();
			break;
		case VK_PRIOR:
			PlaceCaret(m_CaretPos.y - LinesPerScreen(), m_CaretPos.x,
				m_bShift ? selExtend : selRemove, true);
			break;
		case VK_NEXT:
			PlaceCaret(m_CaretPos.y + LinesPerScreen(), m_CaretPos.x,
				m_bShift ? selExtend : selRemove, true);
			break;
		case VK_DELETE:
			Delete();
			break;
		case VK_HOME:
			if (CONTROL_DOWN) {
				PlaceCaret(0, 0, m_bShift ? selExtend : selRemove, true);
			}
			else {
				PlaceCaret(m_CaretPos.y, 0, m_bShift ? selExtend : selRemove, true);
			}
			break;
		case VK_END:
			if (CONTROL_DOWN) {
				int lastLine = m_TextBuffer.LineCount() - 1;
				PlaceCaret(lastLine, m_TextBuffer.LineLength(lastLine),
					m_bShift ? selExtend : selRemove, true);
			}
			else {
				PlaceCaret(m_CaretPos.y, m_TextBuffer.LineLength(m_CaretPos.y),
					m_bShift ? selExtend : selRemove, true);
			}
			break;
	}
}

void CCodeEditWnd::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	switch (nChar) {
		case VK_SHIFT:
			m_bShift = false;
			break;
		case VK_CONTROL:
			//m_bControl = false;
			break;
	}
}

////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////

void CCodeEditWnd::UnmarkSelection()
{
	ASSERT(false);
	m_SelStart = m_SelEnd = m_CaretPos;
	InvalidateRect(NULL);
}

void CCodeEditWnd::SetCurrentLineMark(int line)
{
	m_CurrentLineMark = line;
	InvalidateRect(NULL);
	if (line != -1) PlaceCaret(line, 0, selRemove, true);
}

void CCodeEditWnd::AdjustScrollBarSizes()
{
	RECT rc;
	GetClientRect(&rc);

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
	si.nPage = (rc.bottom - rc.top) / m_FontHeight;
	si.nMin = 0;
	si.nMax = m_TextBuffer.LineCount() - 1;
	si.nPos = m_TopLine;
	SetScrollInfo(SB_VERT, &si);

	si.nPage = (rc.bottom - rc.top) / m_FontWidth;
	si.nMin = 0;
	si.nMax = 256;
	si.nPos = m_LeftCol;
	SetScrollInfo(SB_HORZ, &si);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

void CCodeEditWnd::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	switch (nSBCode) {
		case SB_LINELEFT:
			ScrollLeftRight(m_LeftCol - 1);
			break;
		case SB_LINERIGHT:
			ScrollLeftRight(m_LeftCol + 1);
			break;
		case SB_PAGELEFT:
			ScrollLeftRight(m_LeftCol - ColsPerScreen() - 1);
			break;
		case SB_PAGERIGHT:
			ScrollLeftRight(m_LeftCol + ColsPerScreen() - 1);
			break;
		case SB_THUMBTRACK:
		case SB_THUMBPOSITION:
			ScrollLeftRight(nPos);
			break;
		case SB_TOP:
			break;
		case SB_BOTTOM:
			break;
	}
}

void CCodeEditWnd::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
	switch (nSBCode) {
		case SB_LINEUP:
			ScrollUpDown(m_TopLine - 1);
			break;
		case SB_LINEDOWN:
			ScrollUpDown(m_TopLine + 1);
			break;
		case SB_PAGEUP:
			ScrollUpDown(m_TopLine - LinesPerScreen() - 1);
			break;
		case SB_PAGEDOWN:
			ScrollUpDown(m_TopLine + LinesPerScreen() - 1);
			break;
		case SB_THUMBTRACK:
		case SB_THUMBPOSITION:
			ScrollUpDown(nPos);
			break;
		case SB_TOP:
			break;
		case SB_BOTTOM:
			break;
	}
}

void CCodeEditWnd::ScrollUpDown(int topLine)
{
	m_TopLine = topLine;

	int lps = LinesPerScreen() - 1;
	int count = m_TextBuffer.LineCount();
	if (m_TopLine > count - lps) {
		m_TopLine = count - lps;
	}

	if (m_TopLine < 0) m_TopLine = 0;

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_POS;
	si.nPos = m_TopLine;
	SetScrollInfo(SB_VERT, &si);

	PlaceCaret(m_CaretPos.y, m_CaretPos.x, selMaintain);	// Update caret visual position
	InvalidateRect(NULL);
}

void CCodeEditWnd::ScrollLeftRight(int leftCol)
{
	m_LeftCol = leftCol;

	if (m_LeftCol < 0) m_LeftCol = 0;

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_POS;
	si.nPos = m_LeftCol;
	SetScrollInfo(SB_HORZ, &si);

	PlaceCaret(m_CaretPos.y, m_CaretPos.x, selMaintain);	// Update caret visual position
	InvalidateRect(NULL);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

void CCodeEditWnd::CorrectSelOrder(CPoint & start, CPoint & end)
{
	// Make sure start is "physically" closer to the top of the text
	if ((m_SelStart.y > m_SelEnd.y) || ((m_SelStart.y == m_SelEnd.y) && (m_SelStart.x > m_SelEnd.x))) {
		start = m_SelEnd;
		end = m_SelStart;
	}
	else {
		if (m_SelStart == m_SelEnd) {
			// Just to be sure we use current cursor location
			m_SelStart = m_SelEnd = m_CaretPos;
		}
		start = m_SelStart;
		end = m_SelEnd;
	}
}

void CCodeEditWnd::Copy()
{
	CPoint start, end;
	CorrectSelOrder(start, end);
	m_TextBuffer.Copy(start.y, start.x, end.y, end.x);
}

void CCodeEditWnd::Cut()
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	m_TextBuffer.Cut(start.y, start.x, end.y, end.x);
	PlaceCaret(start.y, start.x, selRemove, true);
	AdjustScrollBarSizes();
	SetModified(true);
}

void CCodeEditWnd::Paste()
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	CPoint point = m_TextBuffer.Paste(start.y, start.x, end.y, end.x);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::CommentOut()
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	CPoint point = m_TextBuffer.CommentOut(start.y, start.x, end.y, end.x);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::Uncomment()
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	CPoint point = m_TextBuffer.Uncomment(start.y, start.x, end.y, end.x);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::Indent()
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	CPoint point = m_TextBuffer.Indent(start.y, start.x, end.y, end.x);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::Unindent()
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	CPoint point = m_TextBuffer.Unindent(start.y, start.x, end.y, end.x);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::Undo()
{
	if (m_bReadOnly) return;
	CPoint point = m_TextBuffer.Undo(m_CaretPos);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::Redo()
{
	if (m_bReadOnly) return;
	CPoint point = m_TextBuffer.Redo(m_CaretPos);
	PlaceCaret(point.y, point.x, selRemove, true);
	AdjustScrollBarSizes();
	InvalidateRect(NULL);
	SetModified(true);
}

void CCodeEditWnd::Delete()
{
	if (m_SelStart == m_SelEnd) {
		m_SelStart = m_SelEnd = m_CaretPos;
		CaretRight(true);
	}
	ReplaceSel(_T(""));
}

void CCodeEditWnd::SelectAll()
{
	m_SelStart = CPoint(0, 0);
	m_SelEnd.y = m_TextBuffer.LineCount() - 1;
	m_SelEnd.x = m_TextBuffer.GetLineOfText(m_SelEnd.y).GetLength();
	InvalidateRect(NULL);
}

bool CCodeEditWnd::CanUndo()
{
	return m_TextBuffer.CanUndo();
}

bool CCodeEditWnd::CanRedo()
{
	return m_TextBuffer.CanRedo();
}

void CCodeEditWnd::ReplaceSel(CString text)
{
	if (m_bReadOnly) return;
	CPoint start, end;
	CorrectSelOrder(start, end);
	m_CaretPos = m_TextBuffer.Replace(text, start.y, start.x, end.y, end.x);
	PlaceCaret(m_CaretPos.y, m_CaretPos.x, selRemove, true);
	AdjustScrollBarSizes();
	SetModified(true);
}

bool CCodeEditWnd::Find(CString text, bool bMatchCase)
{
	((CFrameWnd*)AfxGetMainWnd())->SetMessageText(_T(""));

	int linesInBuffer = m_TextBuffer.LineCount();
	if (linesInBuffer == 0) return false;

	int currentLine = m_CaretPos.y;
	int endLine = currentLine;// > 0 ? currentLine - 1 : linesInBuffer - 1;

	// Always start search at end of current selection
	int startCol = 0;
	if (m_SelStart != m_SelEnd) {
		startCol = m_SelStart.x > m_SelEnd.x ? m_SelStart.x : m_SelEnd.x;
	}

	CString _text = text;
	if (!bMatchCase) _text.MakeLower();

	int pos;
	for (int idx = 0; idx < linesInBuffer + 1; ++idx) {
		CString line = m_TextBuffer.GetLineOfText(currentLine);
		if (startCol > 0) {
			line = line.Mid(startCol);
		}

		if (!bMatchCase) line.MakeLower();
		pos = line.Find(_text);
		if (pos >= 0) {
			pos += startCol;
			PlaceCaret(currentLine, pos, selRemove, true);
			PlaceCaret(currentLine, pos + text.GetLength(), selExtend, false);
			return true;
		}
		startCol = 0;
		if (++currentLine >= linesInBuffer) {
			currentLine = 0;
			((CFrameWnd*)AfxGetMainWnd())->SetMessageText(_T("Passed end of file"));
		}
	}
	MessageBox(_T("Unable to find text in active document: ") + text);
	return false;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////

//
//	The redo stack is implicitly set to the size of the undo stack
//	since it is filled by pops of the undo stack and cleared on
//	pushes to the undo stack.  The undo stack will never have more
//	items to pop from it than it can hold (duh) and therefore the
//	redo stack will never be larger than the undo stack.
//
//	Also, note that the redo stack becomes a place where final
//	deletion of CUndoItems can occur.
//	DO NOT delete a CUndoItem* returned from CUndoStack::Pop()
//	DO NOT delete a CUndoItem* returned from CUndoStack::PopRedo()
//

//==================

CUndoItem::CUndoItem()
{
	m_Type = utUndefined;
}

CUndoItem::CUndoItem(UndoTypes type, CString text, int startLine, int startCol,
					 int endLine, int endCol)
{
	m_Type = type;
	m_Text = text;
	m_StartLine = startLine;
	m_StartCol = startCol;
	m_EndLine = endLine;
	m_EndCol = endCol;

	m_DelToLine = -1;
	m_DelToCol = -1;
}

CUndoItem::CUndoItem(UndoTypes type, CString text, int startLine, int startCol,
					 int endLine, int endCol, CString oldText, int delToLine, int delToCol)
{
	m_Type = type;
	m_Text = text;
	m_StartLine = startLine;
	m_StartCol = startCol;
	m_EndLine = endLine;
	m_EndCol = endCol;
	m_OldText = oldText;
	m_DelToLine = delToLine;
	m_DelToCol = delToCol;
}


CUndoItem::~CUndoItem(){}

//==================

CUndoStack::CUndoStack(int stackSize)
:m_UndoStack(stackSize)
{
	if (stackSize < 1) {
		ASSERT(false);
		m_MaxStackSize = 5;
	}
	else {
		m_MaxStackSize = stackSize;
	}
}

CUndoStack::~CUndoStack()
{
	Clear();
}

bool CUndoStack::IsEmpty()
{
	return m_UndoStack.IsEmpty() == TRUE;
}

bool CUndoStack::IsRedoEmpty()
{
	return m_RedoStack.IsEmpty() == TRUE;
}

void CUndoStack::Clear()
{
	while (!m_UndoStack.IsEmpty()) {
		delete m_UndoStack.RemoveHead();
	}

	ClearRedo();
}

void CUndoStack::ClearRedo()
{
	while (!m_RedoStack.IsEmpty()) {
		delete m_RedoStack.RemoveHead();
	}
}

void CUndoStack::Push(CUndoItem* undoObject)
{
	if (m_UndoStack.GetCount() >= m_MaxStackSize) {
		delete m_UndoStack.RemoveTail();
	}

	m_UndoStack.AddHead(undoObject);

	// Any external push onto the undo stack must always clear the redo stack
	ClearRedo();
}

CUndoItem* CUndoStack::Pop()
{
	if (m_UndoStack.IsEmpty()) return NULL;

	CUndoItem* item = (CUndoItem*)m_UndoStack.RemoveHead();
	m_RedoStack.AddHead(item);

	return item;
}

CUndoItem* CUndoStack::PopRedo()
{
	if (m_RedoStack.IsEmpty()) return NULL;

	CUndoItem* item = (CUndoItem*)m_RedoStack.RemoveHead();
	if (m_UndoStack.GetCount() >= m_MaxStackSize) {
		delete m_UndoStack.RemoveTail();
	}
	m_UndoStack.AddHead(item);

	return item;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#endif // IDE_SUPPORT
